import os
import threading
from time import time
from typing import Optional
from uuid import uuid4

from fastapi import Request, status, BackgroundTasks
from fastapi.responses import Response
from loguru import logger
from starlette.middleware.base import RequestResponseEndpoint, BaseHTTPMiddleware
from starlette.types import ASGIApp
from tenacity import retry, stop_after_attempt, wait_exponential
from user_agents import parse

from app.core.security import decode_token
from app.crud.operation_log_crud import OperationLogCRUD
from app.db.session_factory import get_async_context_manager
from app.deps.crud_deps import get_global_crud_factory
from app.models import OperationLog
from app.schemas.base_schema import LoginAccountType, DeviceType
from app.schemas.operation_log_schema import OperationLogCreate


class OperationLogMiddleware(BaseHTTPMiddleware):
    def __init__(self, app: ASGIApp) -> None:
        super().__init__(app)

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        # 排除不需要记录日志的路径
        excluded_paths = ["/login","/refresh_token", "/redoc", "/docs",  "/openapi.json"]
        if any(path in request.url.path for path in excluded_paths):
            return await call_next(request)

        start_time = time()
        response = None
        message = None
        is_success = True
        status_code = status.HTTP_500_INTERNAL_SERVER_ERROR  # 默认值，会在异常时更新

        # 获取BackgroundTasks
        # FastAPI默认在相同线程中执行后台任务
        # 后台任务的本质是"在响应发送后执行"，而不一定是"在新线程中执行"
        # 要真正实现多线程执行，需要使用run_in_threadpool或显式创建线程池
        # 判断后台任务是否执行的正确方式是检查执行时机（是否在响应后）而非线程名
        background_tasks = BackgroundTasks()

        try:
            # 存储请求体（限制大小）
            body = await request.body()
            request.state.body = body[:1024 * 1024] if len(body) > 1024 * 1024 else body

            request.state.background_tasks = background_tasks

            # 执行请求处理
            response = await call_next(request)
            status_code = response.status_code
            is_success = status_code < 400
        except Exception as e:
            # 不处理异常，但提取异常信息供日志记录
            message = str(e)
            is_success = False
            # 从异常对象提取 status_code（如果是 HTTPException）
            status_code = getattr(e, "status_code", 500)
            raise  # 仍抛出异常，由全局异常中间件处理
        finally:
            # 创建后台任务记录日志
            elapsed_time = int((time() - start_time) * 1000)
            log = await self._log_operation(request, elapsed_time, status_code, is_success, response, message)
            # 添加后台任务
            background_tasks.add_task(self.add_log, log)
            # 如果response存在，添加后台任务
            if response:
                response.background = background_tasks

        return response

    """
    stop=stop_after_attempt(3)
        设置重试停止条件
        当函数执行失败后，最多重试 3次（包括最初的第一次尝试）
        总共最多会执行 1（初始） + 3（重试） = 4 次
    wait=wait_exponential(multiplier=1, min=4, max=10)
        设置每次重试之间的等待时间策略
        multiplier=1：等待时间 = 2^attempt * multiplier（秒）
        min=4：最小等待4秒
        max=10：最大等待10秒
    实际重试间隔：
        第一次重试：max(2^1 * 1, 4) = max(2, 4) = 4秒
        第二次重试：max(2^2 * 1, 4) = max(4, 4) = 4秒
        第三次重试：max(2^3 * 1, 4) = max(8, 4) = 8秒
        （第四次尝试将受限于max=10，最多等待10秒）
    """
    @staticmethod
    @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
    async def add_log(log: dict) :
        async with get_async_context_manager() as session:  # 在 with 块内完成所有 session 操作
            try:
                crud = get_global_crud_factory(OperationLogCRUD, OperationLog, session)
                await crud.create(obj=OperationLogCreate(**log))
            except Exception as e:
                logger.exception(f"操作日志记录失败: {str(e)}", exc_info=True)


    @staticmethod
    async def _log_operation(
        request: Request,
        elapsed_time: int,
        status_code: int,
        is_success: bool,
        response: Optional[Response]=None,
        message: Optional[str] = None,
    ) -> dict:
        # 获取请求参数
        params = None
        if request.method in ["POST", "PUT", "PATCH"]:
            try:
                # 检查是否已经有缓存的表单数据
                if hasattr(request, "_form") and request._form is not None:
                    params = dict(request._form)
                # 检查是否已经有缓存的JSON数据
                elif hasattr(request, "_json") and request._json is not None:
                    params = request._json
                else:
                    # 如果都没有，尝试读取request.state.body
                    params = str(request.state.body)
            except Exception as e:
                params = f"<无法获取参数: {str(e)}>"
        elif request.method == "GET":
            params = dict(request.query_params)

        # 用户信息
        user_id = uuid4()
        account = ""
        account_type = LoginAccountType.username
        device_id = uuid4()
        device_type = DeviceType.pc

        token_data = getattr(request.state, "token_data", None)
        if token_data:
            user_id = token_data.sub
            account = token_data.account
            account_type = token_data.account_type
            device_id = token_data.device_id
            device_type = token_data.device_type
        else:
            authorization = str(request.headers.get("Authorization")).split(" ")
            token = authorization[1] if len(authorization) == 2 else None
            if token is not None:
                payload = decode_token(token)
                user_id = payload.get("sub")
                account = payload.get("account")
                account_type = payload.get("account_type")
                device_id = payload.get("device_id")
                device_type = payload.get("device_type")

        # User-Agent 解析
        user_agent = request.headers.get("User-Agent")
        ua = parse(user_agent)
        browser = ua.browser.family if ua.browser else None
        ua_os = ua.os.family if ua.os else None
        ip_address = request.client.host if request.client else None
        params = str(params) if params else None

        # 响应结果收集
        result = None
        try:
            if response:
                result = "<streaming response>" if hasattr(response, "body_iterator") else str(
                    getattr(response, "body", None))
        except Exception as e:
            result = f"<未能获得响应: {str(e)}>"

        # 获取响应结果
        log = {
            "user_id": user_id,
            "account": account,
            "account_type": account_type,
            "ip_address": ip_address,
            "browser": browser,
            "os": ua_os,
            "device_id": device_id,
            "device_type": device_type,
            "user_agent": user_agent,
            "elapsed_time": elapsed_time,
            "status_code": status_code,
            "is_success": is_success,
            "message": message,
            "api_path": request.url.path,
            "http_method": request.method,
            "result": result,
            "params": params,
        }

        return log